﻿using Color = LightCAD.MathLib.Color;
using LightCAD.MathLib;

namespace LightCAD.Model
{
    public class Model3DEditRuntime:IDocumentEditor
    {
        private WebGLRenderer renderer;
        private Camera camera;
        private Scene scene;
        private RenderCanvas canvas = new RenderCanvas();
        private OrbitControls controls;
        private Group ModelGroup;
        private Group SectionGroup;
        private Group noDrawingGroup;
        private Group buildingsGroup;
        private Group drawingsGroup;
        private ConcurrentDictionary<LcDrawing, Group> drawingGroup=new ConcurrentDictionary<LcDrawing, Group>();
        private ConcurrentDictionary<LcBuilding, Group> buildingGroup = new ConcurrentDictionary<LcBuilding, Group>();
        private ClipBox clipBox;
        public DocumentRuntime DocRt { get; set; }
        public IModel3DEditControl Control { get; set; }

        public EditorType EditorType => EditorType.Model3d;

        public CommandCenter CommandCenter { get; }
        public string Name { get;private set; } = "模型";

        public Model3DEditRuntime(DocumentRuntime docRt, IModel3DEditControl control,ICommandControl commandControl)
        {
            DocRt = docRt;
            Control = control;
            this.Control.AttachEvents(this.OnInit, this.OnDestory, this.OnMouseEvent, this.OnKeyDown, this.OnKeyUp, this.OnSizeChanged, this.OnRender);
            this.DocRt.Document.ModelSpace.ObjectChangedAfter += ModelSpace_ObjectChangedAfter;
            this.DocRt.Document.ModelSpace.PropertyChangedAfter += ModelSpace_PropertyChangedAfter;
            this.DocRt.Action.SelectsChanged += DocumentAction_SelectsChanged;
            this.Control.OnLevelFilterChanged += Control_OnLevelFilterChanged;
            this.CommandCenter = new CommandCenter(this, commandControl);
            this.WallMat.clippingPlanes = Clipplanes;
            this.DoorMat.clippingPlanes = Clipplanes;
            this.DoorKnotMat.clippingPlanes = Clipplanes;
            this.WindowMat.clippingPlanes = Clipplanes;
            this.WinGlassMat.clippingPlanes = Clipplanes;
            this.LadderMat.clippingPlanes = Clipplanes;
            this.PlatMat.clippingPlanes = Clipplanes;
            this.SlabMat.clippingPlanes = Clipplanes;
            this.DefaultMat.clippingPlanes = Clipplanes;
            this.SelectMats.ForEach(s => s.clippingPlanes = Clipplanes);
        }

        private void Control_OnLevelFilterChanged(List<LcLevel> levels)
        {
            foreach (var item in buildingGroup)
            {
                var bgrp = item.Value;
                for (int i = 0; i < bgrp.children.Length; i++)
                {
                    var lvlGrp = bgrp.children[i];
                    var lvl = lvlGrp.ext["Level"] as LcLevel;
                    lvlGrp.visible = levels.Any(l => lvl == l);
                } 
            }
            this.Control.RefreshControl();
        }

        private ListEx<Material> SelectMats = new ListEx<Material> { new MeshBasicMaterial{ color=new Color(0x0000FF),transparent=true,opacity=0.5 }};
        private ConcurrentDictionary<int, ListEx<Material>> OriginMaterials = new ConcurrentDictionary<int, ListEx<Material>>();
       
        private bool updating=false;
        private void ModelSpace_PropertyChangedAfter(object? sender, PropertyChangedEventArgs e)
        {
            update();
        }
        private void ModelSpace_ObjectChangedAfter(object? sender, ObjectChangedEventArgs e)
        {
            update();
        }
        void update()
        {
            if (updating) return;
            updating = true;
            Task.Run(() => { 
            Thread.Sleep(10);
            this.OnUpdate();
            updating = false;
            });
        }

        internal void Dispose()
        {
            this.Control.DetachEvents(this.OnInit, this.OnDestory, this.OnMouseEvent, this.OnKeyDown, this.OnKeyUp, this.OnSizeChanged, this.OnRender);
        }
        private void OnInit()
        {
            this.canvas.Set((int)this.Control.Bounds.Width, (int)this.Control.Bounds.Height);

            this.scene = new Scene();
            var ambientLight = new AmbientLight(0xcccccc, 0.4);
            scene.add(ambientLight);

            var dirLight1 = new DirectionalLight(0xffffff, 0.8);
            dirLight1.position.Set(1, 2, 3);
            scene.add(dirLight1);

            var dirLight2 = new DirectionalLight(0xffffff, 0.8);
            dirLight2.position.Set(-1, -2, -3);
            scene.add(dirLight2);

            this.ModelGroup = new Group();
            this.ModelGroup.name="模型组";
            scene.add(this.ModelGroup);

            this.SectionGroup=new Group();
            this.SectionGroup.name="剖切组";
            this.SectionGroup.visible = false;
            scene.add(this.SectionGroup);

            this.clipBox = new ClipBox(this.SectionGroup);
            this.noDrawingGroup = new Group();
            this.noDrawingGroup.name = "无图元素组";
            this.ModelGroup.add(this.noDrawingGroup);

            this.drawingsGroup = new Group();
            this.drawingsGroup.name = "未使用的图框元素组";
            this.ModelGroup.add(this.drawingsGroup);

            this.buildingsGroup = new Group();
            this.buildingsGroup.name = "单体元素组";
            this.ModelGroup.add(this.buildingsGroup);

            var geometry = new BoxGeometry(200, 200, 200);
            //var material = new MeshBasicMaterial{ color = new LightCAD.Three.Color(0xff0000) });
            var material = new MeshPhongMaterial{ color = new Color(0xff0000), side = MathLib.Constants.DoubleSide };//map = map,

            var ratio = canvas.Width / canvas.Height;
            //camera = new PerspectiveCamera(70, ratio, 1, 20000);
            camera = new OrthographicCamera(-canvas.Width / 2, canvas.Width / 2, canvas.Height / 2, -canvas.Height / 2, 0.1, 200000);
            camera.quaternion.OnChange(() =>
            {
                camera.rotation.SetFromQuaternion(camera.quaternion, camera.rotation.Order, true);
            });
            camera.rotation.OnChange(() =>
            {
                if (Math.Abs(camera.rotation.X - Math.PI / 2) < 1e-3)
                    camera.up.Set(0, -1, 0);
                else if (Math.Abs(camera.rotation.X + Math.PI / 2) < 1e-3)
                    camera.up.Set(0, 1, 0);
                else camera.up.Set(0, 0, 1);
                camera.updateProjectionMatrix();
            });
            camera.position.Copy(syncCameraOffset);
            camera.layers.enable(clipBox.UnSelectChannel);
            camera.layers.enable(ClipPlane.ControlPointLayerChanel);
            //camera.position.set(-5000, 5000, 5000);
            camera.zoom = 0.05;
            camera.up.Set(0, 0, 1);
            camera.lookAt(new Vector3());

            var axesHelper = new AxesHelper(2000);
            scene.add(axesHelper);

            this.canvas = new RenderCanvas().Set(canvas.Width, canvas.Height);
            this.renderer = new WebGLRenderer(canvas);
            //renderer.setSize(glControl.Width, glControl.Height);
            controls = new OrbitControls(camera, this.Control);
            controls.screenSpacePanning = true;
            controls.addEventListener("change", (e) =>
            {
                this.Control.RefreshControl();
            });
            update();
        }
        private void OnDestory()
        {
            this.controls.dispose();
            this.renderer.dispose();
        }


        public void OnSizeChanged(Bounds bounds)
        {
            this.renderer.setSize(bounds.Width, bounds.Height);
            //canvas.Set((int)bounds.Width, (int)bounds.Height);

            if (camera is PerspectiveCamera)
            {
                (camera as PerspectiveCamera).aspect = (double)this.canvas.Width / this.canvas.Height;
            }
            else
            {
                var oriCamera = camera as OrthographicCamera;
                oriCamera.left = -this.canvas.Width / 2.0;
                oriCamera.right = this.canvas.Width / 2.0;
                oriCamera.top = this.canvas.Height / 2.0;
                oriCamera.bottom = -this.canvas.Height / 2.0;
            }
            camera.updateProjectionMatrix();
            //this.renderer.setViewport(0, 0, canvas.Width, canvas.Height);
            this.controls.update();
            //this.Control.RefreshControl();
        }



        #region Events

        internal virtual void OnMouseEvent(string type, MouseEventRuntime e)
        {
            if (type == "Down") OnMouseDown(e);
            else if (type == "Move") OnMouseMove(e);
            else if (type == "Up") OnMouseUp(e);
            else if (type == "Wheel") OnMouseWheel(e);
        }
        private bool clipMove = false;
        private ClipPlane clipPlane = null;
        private DateTime? downTime = null;
        private void OnMouseDown(MouseEventRuntime e)
        {
           downTime = DateTime.Now;
            RayCasterClipBoxControlPoint();
        }
        private void RayCasterClipBoxControlPoint()
        {
            Raycaster raycaster = new Raycaster().setFromCamera(mouseLoc, camera);
            raycaster.layers.set(ClipPlane.ControlPointLayerChanel);
            var intersects = raycaster.intersectObject(this.scene, true);
            if (intersects.Count > 0)
            {
                var mesh = intersects[0].target as Mesh;
                if (mesh.ext.ContainsKey("ClipPlane"))
                {
                    clipMove = true;
                    this.controls.enabled = false;
                    clipPlane = mesh.ext["ClipPlane"] as ClipPlane;
                    clipPlane.Drag();
                }
                this.Control.RefreshControl();
            }
        }
        private void RayCasterEle() 
        {
            Raycaster raycaster = new Raycaster().setFromCamera(mouseLoc, camera);
            var intersects = raycaster.intersectObject(this.scene, true);
            bool needRefresh = false;
            if (this.SectionGroup.visible)
            {
                intersects.RemoveAll(inter => !clipBox.Box.ContainsPoint(inter.point));
            }
            if (intersects.Count > 0)
            {
                var mesh = intersects[0].target as Mesh;
                var selEle = mesh.ext["Element"] as LcElement;
                SelectEle(new List<LcElement> { selEle });
                for (int i = 0; i < EleMeshes[selEle.Id].Length; i++)
                {
                    var emesh = EleMeshes[selEle.Id][i];
                    if (emesh.ext["Level"] == mesh.ext["Level"])
                        SelectMesh(emesh);
                }
                needRefresh = true;
            }
            else
            {
                var ele3Ds = this.DocRt.Action.SelectedElements.FindAll(e => e is IElement3D);
                UnSelectEle(ele3Ds);
                var meshes = SelectedMeshs.Values.ToList();
                for (int i = 0; i < meshes.Count; i++)
                {
                    var mesh = meshes[i];
                    UnSelectMesh(mesh);
                    needRefresh = true;
                }
            }
            if (needRefresh)
                this.Control.RefreshControl();
        }

        #region Hover

        private SKPoint preHover;
        private bool isPointerHovering;
        private System.Timers.Timer hoverTimer;
        private void HoverTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
        {
            hoverTimer.Stop();
            this.isPointerHovering = true;
            HoverEventStart();
        }
        private void HoverEventStart()
        {

        }
        private void HoverEventEnd()
        {

        }

        #endregion

        private Vector2 mouseLoc = new Vector2();
        private void OnMouseMove(MouseEventRuntime e)
        {
            var sx = (double)e.Location.X;
            var sy = (double)e.Location.Y;
            var width = this.Control.Bounds.Width;
            var height = this.Control.Bounds.Height;
            sx = sx * 2 / width - 1;
            sy = (height - sy) * 2 / height - 1;
            mouseLoc.Set(sx, sy);
            if (clipMove) 
            {
                clipPlane.Move(camera, mouseLoc);
                clipBox.ClipModel(this.ModelGroup,true);
                this.Control.RefreshControl();
            }
        }
        private void OnMouseUp(MouseEventRuntime e)
        {
            if (clipMove)
            {
                clipMove = false;
                clipPlane.UnDrag();
                this.controls.enabled = true;
            }
            else
            {
                if (downTime!=null&& (DateTime.Now - downTime).Value.TotalMilliseconds <= 200)
                    RayCasterEle();
            }
            downTime = null;
            
        }
        private ConcurrentDictionary<int,Mesh> SelectedMeshs = new ConcurrentDictionary<int, Mesh>();
        private void DocumentAction_SelectsChanged(object? sender, System.EventArgs e)
        {
            var sv= SelectedMeshs.Values.ToList();
            sv.ForEach(mesh => { UnSelectMesh(mesh); });
            var selEles= this.DocRt.Action.SelectedElements.FindAll(e => e is IElement3D);
            selEles.ForEach(se =>
            {
                if (EleMeshes.ContainsKey(se.Id))
                    EleMeshes[se.Id].ForEach(m => SelectMesh(m));
            });
            this.Control.RefreshControl();
        }
        private void SelectMesh(Mesh mesh)
        {
            OriginMaterials.GetOrAdd(mesh.id, mesh.materials);
            mesh.materials = SelectMats;
            this.SelectedMeshs.GetOrAdd(mesh.id, mesh);
        }
        private void UnSelectMesh(Mesh mesh)
        {
            if (mesh.materials == SelectMats)
                mesh.materials = OriginMaterials[mesh.id];
            this.SelectedMeshs.Remove(mesh.id,out var nmesh);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="selEles"></param>
        private void SelectEle(List<LcElement> selEles)
        {
            try
            {
                this.DocRt.Action.SelectElements(this, selEles);
            }
            catch (Exception ex)
            {
                Debug.Print(ex.Message+ex.StackTrace);
            }
            finally
            {
            }
        }
        private void UnSelectEle(List<LcElement> selEles)
        {
            this.DocRt.Action.ClearSelects(this,selEles.FindAll(e => e.IsSelected));
        }

        private void OnMouseWheel(MouseEventRuntime e)
        {

        }

        internal virtual void OnKeyDown(KeyEventRuntime e)
        {
            if (e.KeyCode == 17)//Ctrl
            {

            }
            else if (e.KeyCode == 27)//ESC
            {
            }

            if (e.KeyModifiers == LcKeyModifiers.Control)
            {

            }
            else if (e.KeyCode == 46)//Delete
            {
                this.CommandCenter.Execute("ERASE");
            }

            //处理在位输入及Esc/Copy，Cut/Paste将启动相应Action
        }

        internal virtual void OnKeyUp(KeyEventRuntime e)
        {
            if (e.KeyCode == 17)//Ctrl
            {

            }

        }

        #endregion
        internal virtual void OnRender()
        {
            this.renderer.setClearColor(13426943);
            this.renderer.render(this.scene, camera);
        }
        public void OnUpdate()
        {
            Debug.Print("Update:" + DateTime.Now);
            try
            {
                var doc = this.DocRt.Document;
                var result = ElementFilter.GroupByDrawings(doc.ModelSpace.Elements);
                this.Control.Load(doc);
                if (result.Item1.Count > 0)
                {
                    var usedDrawings = new List<LcDrawing>();
                    buildingsGroup.removeAll();
                    foreach (var building in doc.Buildings)
                    {
                        var used = CreateBuilding(building, result.Item1);
                        usedDrawings.AddRange(used);
                    }
                    drawingsGroup.removeAll();
                    foreach (var drawing in result.Item1.Keys)
                    {
                        if (usedDrawings.Contains(drawing)) continue;
                        CreateDrawing(drawing, result.Item1[drawing]);
                    }
                }
                this.noDrawingGroup.removeAll();
                if (result.Item2.Count > 0)
                {
                    CreateNoDrawingElements(result.Item2);
                }
            }
            catch (Exception ex)
            {


            }
            this.Control.RefreshControl();
        }
        private ListEx<Plane> Clipplanes = new ListEx<Plane>();
        private  MeshPhongMaterial WallMat = new MeshPhongMaterial
        { color = new Color(0xc0c0c0) };
        private  MeshPhongMaterial DoorMat = new MeshPhongMaterial
        { color = new Color(0xffcc66) };
        private  MeshPhongMaterial DoorKnotMat = new MeshPhongMaterial
        { color = new Color(0x669900)};
        private  MeshPhongMaterial WindowMat = new MeshPhongMaterial
        { color = new Color(0x333300)};
        private  MeshPhongMaterial WinGlassMat = new MeshPhongMaterial
        { color = new Color(0xb3d9ff), opacity= 0.3 ,transparent=true};
        private  MeshPhongMaterial LadderMat = new MeshPhongMaterial
        { color = new Color(0xe8e1d1) };
        private  MeshPhongMaterial PlatMat = new MeshPhongMaterial
        { color = new Color(0xfbf9f3) }; 
        private  MeshPhongMaterial SlabMat = new MeshPhongMaterial
        { color = new Color(0xEED8AE),roughness=0.5 };
        private  MeshPhongMaterial DefaultMat = new MeshPhongMaterial
        { color = new Color(0xBEBEBE) };
        private ConcurrentDictionary<long, ListEx<Mesh>> EleMeshes = new ConcurrentDictionary<long, ListEx<Mesh>>();
        private void CreateNoDrawingElements(List<LcElement> elements)
        {
            CreateElements(this.noDrawingGroup,null ,elements);
        }
        private void CreateElements(Group grp,LcLevel level,List<LcElement> elements) 
        {
            var eles = elements.Where(e => e is IElement3D).ToList();
            if (eles.Count == 0) return;

            grp.removeAll();
            foreach (var ele in eles)
            {
                var eleAction = (ele as IElement3D).Rt3DAction as Element3DAction;
                var geoArr = eleAction.CreateGeometry3D(level,ele, Matrix3.Identity);
                var meshes = new ListEx<Mesh>();
                if (ele.Type.Name == "Wall")
                {
                    var mesh = new Mesh(geoArr[0], WallMat);
                    meshes.Add(mesh);
                }
                else if (ele.Type.Name == "Door")
                {
                    var mesh = new Mesh(geoArr[0], DoorMat);
                    meshes.Add(mesh);
                    var meshKnot = new Mesh(geoArr[1], DoorKnotMat);
                    meshes.Add(meshKnot);
                }
                else if (ele.Type.Name == "Window")
                {
                    var mesh = new Mesh(geoArr[0], WindowMat);
                    meshes.Add(mesh);
                    var meshGlass = new Mesh(geoArr[1], WinGlassMat);
                    meshes.Add(meshGlass);
                }
                else if (ele.Type.Name == "Stair")
                {
                    var mesh = new Mesh(geoArr[0], LadderMat);
                    meshes.Add(mesh);
                    mesh = new Mesh(geoArr[1], LadderMat);
                    meshes.Add(mesh);
                    mesh = new Mesh(geoArr[2], PlatMat);
                    meshes.Add(mesh);
                    mesh = new Mesh(geoArr[3], PlatMat);
                    meshes.Add(mesh);
                    mesh = new Mesh(geoArr[4], PlatMat);
                    meshes.Add(mesh);
                }
                else if (ele.Type.Name == "Slab")
                {
                    foreach (var geo in geoArr)
                    {
                        var mesh = new Mesh(geo, SlabMat);
                        meshes.Add(mesh);
                    }
                }
                for (int i = 0; i < meshes.Length; i++)
                {
                    var mesh = meshes[i];
                    mesh.name = ele.Id.ToString();
                    mesh.ext["Element"] = ele;
                    mesh.ext["Level"] = level;
                    if (ele.IsSelected)
                    {
                        SelectMesh(mesh);
                    }
                }
                EleMeshes.AddOrUpdate(ele.Id, id => meshes, (id, ms) => 
                {
                    ms.RemoveAll(m => m.ext["Level"] == level);
                    ms.Push(meshes.ToArray());
                    return ms;
                });
                grp.add(meshes.ToArray());
            }
        }

        private void CreateDrawing(LcDrawing drawing, List<LcElement> lcElements)
        {
            var drawingGrp = drawingGroup.GetOrAdd(drawing, (d) =>
            {
                var grp = new Group { name = d.Name };
                return grp;
            });
            drawingsGroup.add(drawingGrp);
            CreateElements(drawingGrp,null, lcElements);
        }

        private List<LcDrawing> CreateBuilding(LcBuilding building, Dictionary<LcDrawing, List<LcElement>> item1)
        {
            var buildingGrp= buildingGroup.GetOrAdd(building, (b) => 
            {
                var grp = new Group { name = b.Name };
                return grp; 
            });
            buildingGrp.removeAll();
            buildingsGroup.add(buildingGrp);
            var usedDrawings=new List<LcDrawing>();
            foreach (var level in building.Levels)
            {
                var z = level.Elevation;
                var lvlGrp=new Group { name = level.Name };
                lvlGrp.position.Z = z;
                lvlGrp.ext["Level"] = level;
                buildingGrp.add(lvlGrp);
                for (int j = 0; j < level.Drawings.Count; j++)
                {
                    var drawing = level.Drawings[j];
                    if (drawing.buildingBasePoint == null)
                        continue;
                    var drawingGroup = new Group();
                    var basePoint = drawing.buildingBasePoint.Point;
                    drawingGroup.position.Set(-basePoint.X, -basePoint.Y, 0);
                    CreateElements(drawingGroup, level,drawing.Elements.Values.ToList());
                    lvlGrp.add(drawingGroup);
                    usedDrawings.Add(drawing);
                }
            }
            return usedDrawings;
        }

      
        private readonly Vector3 syncCameraOffset= new Vector3(-10000,-10000,50000);
        //public virtual void SyncViewPort2D(ViewportRuntime  viewportRt) 
        //{
        //    if (this.camera is OrthographicCamera)
        //    {
        //        var vc = viewportRt.DcsToWcs.MultiplyPoint(new Vector2d(0, 0));
        //        var scale = viewportRt.Viewport.Scale;
        //        var orCamera= (OrthographicCamera)this.camera;
        //        orCamera.zoom = scale/2*((double)canvas.Width/viewportRt.Viewport.Width);
        //        orCamera.position.AddVectors(new Vector3(vc.X,vc.Y,0), syncCameraOffset);
        //        var target = new Vector3(vc.X, vc.Y, 0);
        //        //orCamera.up.set(0, 1, 0);
        //        orCamera.lookAt(target);
        //        controls.target.Copy(target);
        //    }
        //    this.Control.RefreshControl();
        //}
        private bool clipBoxEnable = false;
        public void EnbaleClipBox() 
        {
            clipBoxEnable = !clipBoxEnable;
            if (clipBoxEnable)
            {
                this.SectionGroup.visible = true;
                this.clipBox.Set(GeoUtil.ComputeWorldBBox(this.ModelGroup), this.Clipplanes);
                this.renderer.localClippingEnabled = true;
            }
            else
            {
                this.SectionGroup.visible = false;
                this.renderer.localClippingEnabled = false;
            }
            this.Control.RefreshControl();
        }

        public void CancelAll()
        {
        }

        public void CommandExecute(LcCommand cmd)
        {
        }

        public void SetActive(bool active)
        {
            if (active)
                this.CommandCenter.SetActive();
            this.Control.SetActive(active);
        }
    }
}
